﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Data;

using Shared;
using JoltEnvironment.Sql;

namespace ClusterServer
{
    public partial class Client
    {
        #region Handler

        /// <summary>
        /// Defines on what position the header is on.
        /// </summary>
        private const int HeaderPoint = 17;

        /// <summary>
        /// Handles an incoming packet for the client.
        /// </summary>
        /// <param name="Packet">Packet to be handled.</param>
        public void HandlePacket(DataPacket Packet)
        {
            if (this.Remains != null)
            {
                byte[] Buffer = new byte[this.Remains.Length + Packet.Buffer.Length];

                Array.Copy(this.Remains, 0, Buffer, 0, this.Remains.Length);
                Array.Copy(Packet.Buffer, 0, Buffer, 0, Packet.Buffer.Length);

                this.Remains = null;
            }

            if (!Packet.VerifyHeaders(this.SessionKey))
            {
                Remains = Packet.Buffer;
            }
            else
            {
                Packet.Pointer = Client.HeaderPoint;
                int Header = Packet.ReadInt32();

                this.PacketTick = (uint)Tick.GetTickCount();

                if ((Packet.Size - Client.HeaderPoint) >= 4)
                {
                    int OldPointer = Packet.Pointer;
                    Packet.Pointer = Packet.Size - 4;

                    this.ClientState = Packet.ReadInt32();

                    Packet.Pointer = OldPointer;
                }

                switch (Header)
                {
                    case MessageHeaders.IN_PING_UNKNOWN:
                        {
                            this.SendUnknown();
                            this.PingState++;
                        }
                        break;

                    case MessageHeaders.IN_PING:
                        {
                            this.HandlePing();
                        }
                        break;

                    case MessageHeaders.IN_CHARACTER_LIST:
                        {
                            this.HandleCharacterList(Packet);
                        }
                        break;

                    case MessageHeaders.IN_CHARACTER_CREATE:
                        {
                            this.HandleCharacterCreate(Packet);
                        }
                        break;

                    case MessageHeaders.IN_CHARACTER_DELETE:
                        {
                            this.HandleCharacterDelete(Packet);
                        }
                        break;

                    case MessageHeaders.IN_WORLD_TRANSFER:
                        {
                            this.HandleWorldTransfer(Packet);
                        }
                        break;

                    default:
                        {
                            this.Write("Unknown packet header received: {0}", Header);
                        }
                        break;
                }
            }
        }

        #endregion

        #region Incoming Handlers

        /// <summary>
        /// Handles incoming ping from client.
        /// </summary>
        private void HandlePing()
        {
            this.SendPong();
        }

        /// <summary>
        /// Handles incoming character list request from client.
        /// </summary>
        /// <param name="Packet">Packet containing user data.</param>
        private void HandleCharacterList(DataPacket Packet)
        {
            string BuildDate = Packet.ReadString();
            this.ClientState = Packet.ReadInt32();

            string Username = Packet.ReadString();
            string Password = Packet.ReadString();

            int ServerID = Packet.ReadInt32();

            if (BuildDate != Program.Config.Get("Game.Version", "20060629"))
            {
                this.Destruct("Invalid game version.");
            }
            else
            {
                DataRow Account = null;

                using (SqlDatabaseClient Client = Program.DatabaseManager.GetClient())
                {
                    Client.AddParameter("username", Username);

                    Account = Client.ReadDataRow("SELECT * FROM account_tbl WHERE username=@username;");
                }

                if (Account == null)
                {
                    this.Destruct("Account doesn't exist.");
                }
                else
                {
                    int Authority = (int)Account["authority"];
                    string RealPassword = (string)Account["password"];

                    if (Authority < 100)
                    {
                        this.Destruct("Account is banned.");
                    }
                    else if (Password.ToLower() != RealPassword.ToLower())
                    {
                        this.Destruct("Wrong password.");
                    }
                    else
                    {
                        World WorldData;

                        try
                        {
                            WorldData = ServerManager.GetWorld(ServerID);
                        }
                        catch
                        {
                            this.Destruct("World doesn't exist.");
                            return;
                        }

                        if (this.ClientSocket.RemoteEndPoint.ToString().StartsWith("127.0.0.1"))
                        {
                            WorldData.WorldIP = "127.0.0.1";
                        }

                        this.UserID = (int)Account["id"];
                        this.Username = Username;
                        this.Password = Password;

                        this.LoadCharacters();
                        this.SendWorldIP(WorldData.WorldIP);
                        this.SendCharacterList();
                    }
                }
            }
        }

        /// <summary>
        /// Handles a character creation request from client.
        /// </summary>
        /// <param name="Packet">Packet containing creation data.</param>
        private void HandleCharacterCreate(DataPacket Packet)
        {
            string Username = Packet.ReadString();
            string Password = Packet.ReadString();

            if (Username.ToLower() != this.Username.ToLower() || Password.ToLower() != this.Password.ToLower())
            {
                this.Destruct("Invalid username/password received on character create.");
                return;
            }

            int Slot = Packet.ReadByte();
            string Name = Packet.ReadString();

            foreach (char Character in Name)
            {
                try
                {
                    int CharInt = Convert.ToInt32(Character);

                    if (!(CharInt > 64 && CharInt < 91) && !(CharInt > 96 && CharInt < 123))
                    {
                        this.SendError(MessageHeaders.ERROR_INVALID_NAME);
                        return;
                    }
                }
                catch
                {
                    this.SendError(MessageHeaders.ERROR_INVALID_NAME);
                    return;
                }
            }

            DataRow CharacterExists = null;
            DataRow SlotIsTaken = null;

            using (SqlDatabaseClient Client = Program.DatabaseManager.GetClient())
            {
                Client.AddParameter("account_id", this.UserID);
                Client.AddParameter("name", Name);
                Client.AddParameter("slot", Slot);

                CharacterExists = Client.ReadDataRow("SELECT id FROM character_tbl WHERE name=@name;");
                SlotIsTaken = Client.ReadDataRow("SELECT id FROM character_tbl WHERE slot=@slot AND account_id=@account_id;");
            }

            if (SlotIsTaken != null)
            {
                this.SendError(MessageHeaders.ERROR_DUPLICATE_SLOT);
                return;
            }

            if (CharacterExists != null)
            {
                this.SendError(MessageHeaders.ERROR_CHARACTER_EXISTS);
                return;
            }

            Packet.Pointer += 3;
            int HairID = Packet.ReadByte();
            int HairColor = Packet.ReadInt32();
            int Gender = Math.Min((byte)1, Math.Max((byte)0, Packet.ReadByte()));

            Packet.Pointer++;
            int FaceID = Packet.ReadByte();

            Packet.Pointer = Packet.Size - 4;
            this.ClientState = Packet.ReadInt32();

            using (SqlDatabaseClient Client = Program.DatabaseManager.GetClient())
            {
                Client.AddParameter("account_id", this.UserID);
                Client.AddParameter("cluster_id", Program.ClusterManager.ID);

                Client.AddParameter("name", Name);
                Client.AddParameter("slot", Slot);

                Client.AddParameter("map_id", 1);

                Client.AddParameter("pos_x", 6966);
                Client.AddParameter("pos_y", 100);
                Client.AddParameter("pos_z", 3333);

                Client.AddParameter("hair_id", HairID);
                Client.AddParameter("hair_color", HairColor);
                Client.AddParameter("face_id", FaceID);
                Client.AddParameter("gender", Gender);

                Client.AddParameter("class", 0);
                Client.AddParameter("level", 1);
                Client.AddParameter("experience", 0);
                Client.AddParameter("stat_points", 0);
                Client.AddParameter("skill_points", 0);
                Client.AddParameter("money", 0);

                Client.AddParameter("strength", 15);
                Client.AddParameter("stamina", 15);
                Client.AddParameter("dexterity", 15);
                Client.AddParameter("intelligence", 15);

                Client.AddParameter("hp", 100);
                Client.AddParameter("mp", 10);
                Client.AddParameter("fp", 10);

                Client.AddParameter("fly_level", 1);
                Client.AddParameter("fly_experience", 0);

                Client.AddParameter("pvp", 0);
                Client.AddParameter("pk", 0);
                Client.AddParameter("disposition", 0);

                Client.AddParameter("party", 0);
                Client.AddParameter("guild", 0);

                Client.AddParameter("pxp1", 0);
                Client.AddParameter("pxp2", 0);

                Client.ExecuteUpdate(
                    "INSERT INTO character_tbl("
                    + "account_id, cluster_id, name, slot, map_id, hair_id, hair_color, face_id,"
                    + "gender, class, level, experience, stat_points, skill_points, money, strength,"
                    + "stamina, dexterity, intelligence, pos_x, pos_y, pos_z, hp, mp, fp, fly_level,"
                    + "fly_experience, pvp, pk, disposition, party, guild, pxp1, pxp2"
                    + ") VALUES("
                    + "@account_id, @cluster_id, @name, @slot, @map_id, @hair_id, @hair_color, @face_id,"
                    + "@gender, @class, @level, @experience, @stat_points, @skill_points, @money, @strength,"
                    + "@stamina, @dexterity, @intelligence, @pos_x, @pos_y, @pos_z, @hp, @mp, @fp, @fly_level,"
                    + "@fly_experience, @pvp, @pk, @disposition, @party, @guild, @pxp1, @pxp2"
                    + ");"
                   );

                DataRow IdRow = null;
                IdRow = Client.ReadDataRow("SELECT id FROM character_tbl WHERE name=@name AND cluster_id=@cluster_id;");

                if (IdRow == null)
                {
                    this.Destruct("Could not get id from character upon creation.");
                    return;
                }

                int CharacterID = (int)IdRow["id"];
                Client.AddParameter("character_id", CharacterID);

                Client.ExecuteUpdate("SELECT character_create_str(@character_id, @gender);");
            }

            this.LoadCharacters();
            this.SendCharacterList();
        }

        /// <summary>
        /// Handles a character deletion request from client.
        /// </summary>
        /// <param name="Packet">Packet containing deletion data.</param>
        private void HandleCharacterDelete(DataPacket Packet)
        {
            string Username = Packet.ReadString();
            string Password = Packet.ReadString();

            if (Username.ToLower() != this.Username.ToLower() || Password.ToLower() != this.Password.ToLower())
            {
                this.Destruct("Invalid username/password received on character delete.");
                return;
            }

            Packet.ReadString();

            int CharacterID = Packet.ReadInt32();
            this.ClientState = Packet.ReadInt32();

            DataRow Delete = null;

            using (SqlDatabaseClient Client = Program.DatabaseManager.GetClient())
            {
                Client.AddParameter("id", CharacterID);

                Delete = Client.ReadDataRow("SELECT character_delete_str(@id) AS delete_status;");
            }

            if ((int)Delete["delete_status"] == -1)
            {
                this.Destruct("Invalid character id on character delete.");
                return;
            }

            this.LoadCharacters();
            this.SendCharacterList();
        }

        /// <summary>
        /// Handles a world-transfer request from client.
        /// </summary>
        /// <param name="Packet">Packet containing transfer data.</param>
        private void HandleWorldTransfer(DataPacket Packet)
        {
            string Username = Packet.ReadString();

            if (Username.ToLower() != this.Username.ToLower())
            {
                this.Destruct("Invalid username received on character world transfer.");
            }

            if (this.PingState > 0)
            {
                this.SendWorldTransfer();
            }
        }

        #endregion
    }
}
